﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ArtheaEngine;
using NLog;
using Object=ArtheaEngine.Object;

namespace ArtheaEditor
{
    public class RomImporter
    {
        private static readonly Logger log = LogManager.GetCurrentClassLogger();
        private readonly Dictionary<uint, NonPlayer> characterIndexes = new Dictionary<uint, NonPlayer>();

        private readonly ArtheaDataContext db = new ArtheaDataContext();
        private readonly Dictionary<uint, Object> objectIndexes = new Dictionary<uint, Object>();
        private readonly Dictionary<uint, Room> roomIndexes = new Dictionary<uint, Room>();
        private Area currentArea;
        private FileInfo file;
        private RomReader reader;

        public void Process(FileInfo fileInfo)
        {
            if (fileInfo.Name.EndsWith(".lst"))
            {
                var listReader = new RomReader(fileInfo.FullName);
                string word = listReader.ReadWord();

                int count = 0;

                while (word != "$")
                {
                    try
                    {
                        Process(new FileInfo(fileInfo.DirectoryName + Path.DirectorySeparatorChar + word));
                        count++;
                    }
                    catch (RomImportException ex)
                    {
                        log.Error("Unable to load {0} ({1})", word, ex.Message);
                        return;
                    }
                    word = listReader.ReadWord();
                }

                log.Info("Imported {0} area files.", count);
                return;
            }

            file = fileInfo;

            reader = new RomReader(file.FullName);

            while (!reader.EndOfFile)
            {
                if (reader.ReadLetter() != '#')
                {
                    log.Error("Error in rom area file {0}", file.Name);
                    throw new RomImportException("Invalid file");
                }

                string word = reader.ReadWord();

                switch (word.ToLower())
                {
                    case "$":
                        break;
                    case "areadata":
                        LoadRomArea();
                        break;
                    case "area":
                        LoadOldRomArea();
                        break;
                    case "mobiles":
                        LoadRomMobiles();
                        break;
                    case "objects":
                        LoadRomObjects();
                        break;
                    case "rooms":
                        LoadRomRooms();
                        break;
                    case "resets":
                        LoadRomResets();
                        break;
                    case "helps":
                        LoadRomHelps();
                        break;
                    case "shops":
                        LoadRomShops();
                        break;
                    case "socials":
                        LoadRomSocials();
                        break;
                    case "specials":
                        LoadRomSpecials();
                        break;
                    default:
                        log.Error("Bad section name in ROM area {0}", file.Name);
                        throw new RomImportException("Bad section name");
                }
            }

            if (currentArea != null)
            {
                /*if (!Lists.Areas.Contains(currentArea))
                        currentArea.Attach();

                    foreach (Room room in currentArea.Rooms.Values)
                        if (!Lists.Rooms.ContainsKey(room.Id))
                            Lists.Rooms.Add(room);

                    foreach (ItemIndex item in currentArea.Items.Values)
                        if (!Lists.ItemIndexes.ContainsKey(item.Id))
                            Lists.ItemIndexes.Add(item);

                    foreach (CharIndex mob in currentArea.Characters.Values)
                        if (!Lists.CharIndexes.ContainsKey(mob.Id))
                            Lists.CharIndexes.Add(mob);
                    */
                currentArea.Update();
            }
            db.SubmitChanges();
        }

        /*
            private CharIndex GetCharIndex(uint id)
            {
                if (currentArea.Characters.ContainsKey(id))
                    return currentArea.Characters[id];

                if (Lists.CharIndexes.ContainsKey(id))
                    return Lists.CharIndexes[id];

                throw new RomImportException("Character " + id + " not found.");
            }

            private ItemIndex GetItemIndex(uint id)
            {
                if (currentArea.Items.ContainsKey(id))
                    return currentArea.Items[id];

                if (Lists.ItemIndexes.ContainsKey(id))
                    return Lists.ItemIndexes[id];

                throw new RomImportException("Item " + id + " not found.");
            }

            private Room GetRoom(uint id)
            {
                if (currentArea.Rooms.ContainsKey(id))
                    return currentArea.Rooms[id];

                if (Lists.Rooms.ContainsKey(id))
                    return Lists.Rooms[id];

                throw new RomImportException("Room " + id + " not found.");
            }
            */

        private void LoadRomArea()
        {
            currentArea = new Area();
            //currentArea.FileName = file.Name.Substring(0, file.Name.LastIndexOf('.'));

            string word;

            while (!reader.EndOfFile)
            {
                word = reader.ReadWord();

                switch (word.ToLower())
                {
                    case "credits":
                        currentArea.Credits = reader.ReadString();
                        break;
                    case "name":
                        currentArea.Name = reader.ReadString();
                        break;
                    case "security":
                        reader.ReadNumber();
                        break;
                    case "vnums":
                        /*currentArea.BaseId = (uint)*/
                        reader.ReadNumber();
                        /*currentArea.MaxId = (uint)*/
                        reader.ReadNumber();
                        break;
                    case "builders":
                        reader.ReadString();
                        break;
                    case "end":
                        /*Area exists = Lists.Areas.FindById(currentArea.BaseId, currentArea.MaxId);
                            if (exists != null)
                                currentArea = exists;*/
                        db.Areas.InsertOnSubmit(currentArea);
                        return;
                    default:
                        log.Warn("Invalid area key " + word);
                        reader.ReadLine();
                        break;
                }
            }
        }

        private void LoadOldRomArea()
        {
            currentArea = new Area();

            reader.ReadString(); // filename
            currentArea.Name = reader.ReadString();
            currentArea.Credits = reader.ReadString();
            reader.ReadNumber(); // min vnum
            reader.ReadNumber(); // max vnum

            db.Areas.InsertOnSubmit(currentArea);
        }

        private void LoadRomHelps()
        {
            int count = 0;

            while (!reader.EndOfFile)
            {
                var help = new Help();

                int level = reader.ReadNumber();

                if (level >= 51) //help.Category = HelpCategory.Admin;
                    help.Level = World.IMMORTAL;

                help.Keywords = reader.ReadString();

                if (help.Keywords.Contains("$"))
                    break;

                help.Text = reader.ReadString();

                db.Helps.InsertOnSubmit(help);
                count++;
                //Lists.Helps.Add(help);
            }

            log.Info("Loaded {0} helps.", count);
        }

        private void LoadRomMobiles()
        {
            if (currentArea == null)
            {
                throw new RomImportException("LoadRomMobiles: area not seen yet.");
            }

            int count = 0;

            while (!reader.EndOfFile)
            {
                if (reader.ReadLetter() != '#')
                {
                    throw new RomImportException("LoadRomMobiles: # not found");
                }

                var num = (uint) reader.ReadNumber();

                if (num == 0)
                    break;

                var mob = new NonPlayer();


                mob.Name = reader.ReadString();
                mob.ShortDescr = reader.ReadString();
                mob.LongDescr = reader.ReadString();
                mob.Description = reader.ReadString();
                mob.Race = World.Races.FindByName(reader.ReadString());
                if (mob.Race == null)
                    mob.Race = new HumanRace();

                mob.Flags |= RomConvertActFlags(); // act flags
                reader.ReadFlag(); // affect flags;

                reader.ReadNumber(); // alignment
                reader.ReadNumber(); // group

                mob.Level = (short) reader.ReadNumber();

                reader.ReadNumber(); //hitroll

                /* hit dice */
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();

                /* mana dice */
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();

                /* damage dice */
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();
                reader.ReadLetter();
                reader.ReadNumber();

                reader.ReadWord(); // dam type

                for (int i = 0; i < 4; i++)
                    reader.ReadNumber(); // AC

                reader.ReadFlag(); // off bits
                reader.ReadFlag(); // imm bits
                reader.ReadFlag(); // res bits
                reader.ReadFlag(); // vuln bits

                string posName = reader.ReadWord();

                try
                {
                    mob.Position = (Position) Enum.Parse(typeof (Position), posName, true);
                }
                catch (ArgumentException)
                {
                    mob.Position = Position.Standing;
                }

                reader.ReadWord(); // default position

                string sex = reader.ReadWord();

                try
                {
                    mob.Sex = (Sex) Enum.Parse(typeof (Sex), sex, true);
                }
                catch (ArgumentException)
                {
                    mob.Sex = Sex.Neutral;
                }
                reader.ReadNumber(); // wealth

                reader.ReadFlag(); // form bits
                reader.ReadFlag(); // parts bits

                string size = reader.ReadWord();

                try
                {
                    mob.Size = (Size) Enum.Parse(typeof (Size), size, true);
                }
                catch (ArgumentException)
                {
                    mob.Size = Size.Medium;
                }

                reader.ReadWord(); // material

                while (!reader.EndOfFile)
                {
                    char letter = reader.ReadLetter();

                    if (letter == 'F')
                    {
                        string word = reader.ReadWord();
                        reader.ReadFlag();

                        switch (word.ToLower())
                        {
                            case "aff":
                            case "par":
                            case "act":
                            case "off":
                            case "for":
                            case "imm":
                            case "res":
                            case "vul":
                                break;
                            default:
                                throw new RomImportException("LoadRomMobiles: flag " + word + " not found");
                        }
                    }
                    else
                    {
                        reader.UnRead();
                        break;
                    }
                }

                /*
                    if (!currentArea.Characters.ContainsKey(mob.Id))
                        currentArea.Characters.Add(mob);
                     */
                count++;
                characterIndexes.Add(num, mob);
                currentArea.NonPlayers.Add(mob);
                db.Characters.InsertOnSubmit(mob);
            }
            log.Info("Loaded {0} mobiles.", count);
        }

        private void LoadRomObjects()
        {
            int count = 0;

            if (currentArea == null)
            {
                throw new RomImportException("LoadRomObjects: area not seen yet");
            }

            while (!reader.EndOfFile)
            {
                if (reader.ReadLetter() != '#')
                {
                    throw new RomImportException("LoadRomObjects: # not found");
                }

                uint id = Convert.ToUInt32(reader.ReadNumber());

                if (id == 0)
                    break;

                //ItemIndex item = new ItemIndex();

                //item.Id = id;

                string name = reader.ReadString();
                string shortDescr = reader.ReadString();
                string longDescr = reader.ReadString();

                reader.ReadString(); // material

                string type = reader.ReadWord();

                long extraFlags = RomConvertExtraFlags(); // extra flags

                long wearFlags = RomConvertWearFlags(); // wear flags

                Object item;

                switch (type.ToLower())
                {
                    case "weapon":
                        var weaponItem = new WeaponObject();
                        string weaponType = reader.ReadWord(); // weapon type;

                        try
                        {
                            weaponItem.V1 = (WeaponClass) Enum.Parse(typeof (WeaponClass), weaponType, true);
                        }
                        catch (ArgumentException)
                        {
                            weaponItem.V1 = WeaponClass.Exotic;
                        }

                        weaponItem.V2 = reader.ReadNumber();
                        weaponItem.V3 = reader.ReadNumber();
                        weaponItem.V4 = reader.ReadWord(); // attack type
                        weaponItem.V5 = RomConvertWeaponFlags();
                        item = weaponItem;
                        break;
                    case "container":
                        var containerItem = new ContainerObject();
                        containerItem.V1 = reader.ReadNumber();
                        containerItem.V2 = RomConvertContainerFlags();
                        int key = reader.ReadNumber();
                        containerItem.V3 = (key < 0) ? 0 : Convert.ToUInt32(key);
                        reader.ReadFlag();
                        reader.ReadFlag();
                        item = containerItem;
                        break;
                    case "drink":
                    case "fountain":
                        var drinkContainer = new DrinkContainerObject();
                        drinkContainer.V1 = reader.ReadNumber();
                        drinkContainer.V2 = reader.ReadNumber();
                        drinkContainer.V3 = reader.ReadWord(); // liquid type
                        reader.ReadFlag();
                        reader.ReadFlag();
                        item = drinkContainer;
                        break;
                    case "wand":
                        item = RomReadWandOrStaff(new WandObject());
                        break;
                    case "staff":
                        item = RomReadWandOrStaff(new StaffObject());
                        break;
                    case "potion":
                        item = RomReadPotionScrollOrPill(new PotionObject());
                        break;
                    case "pill":
                        item = RomReadPotionScrollOrPill(new PillObject());
                        break;
                    case "scroll":
                        item = RomReadPotionScrollOrPill(new ScrollObject());

                        break;

                    case "food":
                        var foodItem = new FoodObject();
                        int hours;
                        try
                        {
                            hours = Convert.ToInt32(reader.ReadFlag());
                        }
                        catch
                        {
                            hours = 0;
                        }
                        foodItem.V1 = new TimeSpan(hours, 0, 0);
                        reader.ReadFlag();
                        reader.ReadFlag();
                        foodItem.V2 = reader.ReadFlag() > 0;
                        reader.ReadFlag();
                        item = foodItem;
                        break;

                    case "armor":
                        var armorItem = new ArmorObject();
                        armorItem.V1 = Convert.ToInt32(reader.ReadFlag());
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        item = armorItem;
                        break;

                    case "portal":
                        var portalItem = new PortalObject();
                        portalItem.V1 = Convert.ToInt16(reader.ReadFlag());
                        portalItem.V2 = RomConvertExitFlags();
                        portalItem.V3 = RomConvertPortalFlags();
                        portalItem.V4 = Convert.ToUInt32(reader.ReadFlag());
                        reader.ReadFlag();
                        item = portalItem;
                        break;

                    case "furniture":
                        var furnItem = new FurnitureObject();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        RomConvertFurnitureFlags(furnItem);
                        furnItem.V1 = Convert.ToInt32(reader.ReadFlag());
                        furnItem.V2 = Convert.ToInt32(reader.ReadFlag());
                        item = furnItem;
                        break;

                    case "money":
                        var moneyItem = new MoneyObject();
                        moneyItem.V1 = new Money(Convert.ToUInt64(reader.ReadFlag()), MoneyType.Gold);
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        item = moneyItem;
                        break;

                    default:
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        reader.ReadFlag();
                        item = new TrashObject();
                        break;
                }


                item.Name = name;
                item.ShortDescr = shortDescr;
                item.LongDescr = longDescr;
                item.Flags = extraFlags;
                item.WearFlags = wearFlags;
                item.Level = Convert.ToInt16(reader.ReadNumber());
                item.Weight = Convert.ToInt16(reader.ReadNumber());
                item.Cost = new Money(reader.ReadNumber(), MoneyType.Gold);
                item.Area = currentArea;

                switch (reader.ReadLetter())
                {
                    case ('P'):
                        item.Condition = 100;
                        break;
                    case ('G'):
                        item.Condition = 90;
                        break;
                    case ('A'):
                        item.Condition = 75;
                        break;
                    case ('W'):
                        item.Condition = 50;
                        break;
                    case ('D'):
                        item.Condition = 25;
                        break;
                    case ('B'):
                        item.Condition = 10;
                        break;
                    case ('R'):
                        item.Condition = 0;
                        break;
                    default:
                        item.Condition = 100;
                        break;
                }

                while (!reader.EndOfFile)
                {
                    char letter = reader.ReadLetter();

                    if (letter == 'A')
                    {
                        AffectType what = RomConvertAffectWhat();
                        var modifier = (short) reader.ReadNumber();

                        if (what != AffectType.None)
                        {
                            var a = new Affect(0, null, what, modifier, -1);
                            var oa = new ObjectAffect {Affect = a, Object = item};
                            item.Affects.Add(oa);
                        }
                    }
                    else if (letter == 'F')
                    {
                        switch (reader.ReadLetter())
                        {
                            case 'A':
                            case 'I':
                            case 'R':
                            case 'V':
                                break;
                            default:
                                throw new RomImportException("Invalid object flag on item " + item.ObjectId);
                        }

                        reader.ReadNumber(); // location
                        reader.ReadNumber(); // modifier;
                        reader.ReadFlag(); // bitvector
                    }
                    else if (letter == 'E')
                    {
                        //item.ExtraDescriptions.Add(reader.ReadString(),
                        reader.ReadString();
                        reader.ReadString();
                    }
                    else
                    {
                        reader.UnRead();
                        break;
                    }
                } 
                count++;

                objectIndexes.Add(id, item);
                db.Objects.InsertOnSubmit(item);
            }

            log.Info("imported " + count + " items.");
        }

        private void LoadRomResets()
        {
            if (currentArea == null)
            {
                throw new RomImportException("LoadRomResets: area not seen yet");
            }

            NonPlayer lastMob = null;

            int count = 0;

            while (!reader.EndOfFile)
            {
                char letter = reader.ReadLetter();

                if (letter == 'S') break;

                if (letter == '*')
                {
                    reader.ReadLine();
                    continue;
                }

                reader.ReadNumber(); // if_flag

                uint arg1 = Convert.ToUInt32(reader.ReadNumber());
                reader.ReadNumber();
                uint arg3 = (letter == 'G' || letter == 'R')
                                ? 0
                                : Convert.ToUInt32(reader.ReadNumber());
                uint arg4 = (letter == 'P' || letter == 'M')
                                ? Convert.ToUInt32(reader.ReadNumber())
                                : 0;

                reader.ReadLine();

                Reset reset = null;

                switch (letter)
                {
                    case 'M':

                        try
                        {
                            var cr = new CharacterReset();
                           
                            cr.Room = roomIndexes[arg3];
                            lastMob = characterIndexes[arg1];
                            if(lastMob == null)
                            {
                                throw new RomImportException("No such character " + arg1 + " for reset");
                            }
                            cr.NonPlayer = lastMob;
                            cr.Count = Convert.ToInt16(arg4);
                            reset = cr;
                        }
                        catch (Exception ex)
                        {
                            log.Error(ex.Message);
                            throw;
                        }
                        break;
                    case 'O':
                        try
                        {
                            var or = new ObjectReset();

                            or.Room = roomIndexes[arg3];

                            or.Object = objectIndexes[arg1];

                            or.Count = Convert.ToInt16(arg4);

                            reset = or;
                        }
                        catch (Exception ex)
                        {
                            log.Error(ex.Message);
                            throw;
                        }
                        break;
                    case 'P':
                        try
                        {
                            var cr = new ContainerReset();

                            cr.Object = objectIndexes[arg3];

                            cr.Container = objectIndexes[arg1];

                            reset = cr;
                        }
                        catch (Exception ex)
                        {
                            log.Error(ex.Message);
                            throw;
                        }
                        break;
                    case 'E':
                    case 'G':
                        if (lastMob == null)
                        {
                            log.Error("No previous mob for Equip/Give reset, item " + arg1);
                            continue;
                        }

                        try
                        {
                            var er = new EquipmentReset();

                            er.NonPlayer = lastMob;

                            er.Object = objectIndexes[arg1];
                            er.WearLocation = (WearLocation) arg4;

                            reset = er;
                        }
                        catch (Exception ex)
                        {
                            log.Error(ex.Message);
                            continue;
                        }
                        break;

                    case 'D':
                    case 'R':
                        break;

                    default:
                        throw new RomImportException("Unknown reset type " + letter);
                }

                if (reset != null)
                {
                    reset.Area = currentArea;
                    db.Resets.InsertOnSubmit(reset);
                    count++;
                }
            }

            log.Info("Loaded {0} resets.", count);
        }

        private void LoadRomRooms()
        {
            if (currentArea == null)
            {
                throw new RomImportException("LoadRomRooms: No area seen yet.");
            }

            int count = 0;

            while (!reader.EndOfFile)
            {
                if (reader.ReadLetter() != '#')
                {
                    throw new RomImportException("LoadRomRooms: '#' not found.");
                }

                var id = (uint) reader.ReadNumber();

                if (id == 0)
                    break;

                var room = new Room();

                room.Name = reader.ReadString();
                room.Description = reader.ReadString();
                reader.ReadNumber(); // area number
                room.Flags = RomRoomFlagConvert();
                room.Type = RomRoomSectorConvert();

                while (!reader.EndOfFile)
                {
                    char letter = reader.ReadLetter();

                    if (letter == 'S')
                    {
                        break;
                    }
                    if (letter == 'H')
                    {
                        reader.ReadNumber();
                    }
                    else if (letter == 'M')
                    {
                        reader.ReadNumber();
                    }
                    else if (letter == 'C')
                    {
                        reader.ReadString();
                    }
                    else if (letter == 'D')
                    {
                        Direction door = RomDirectionConvert();

                        var exit = new Exit();
                        exit.Description = reader.ReadString();
                        exit.Name = reader.ReadString();
                        int locks = reader.ReadNumber();
                        int key = reader.ReadNumber();
                        exit.Key = db.Objects.SingleOrDefault(o => o.ObjectId == key);
                        int toRoom = reader.ReadNumber();
                        exit.ToRoom = db.Rooms.SingleOrDefault(r => r.RoomId == toRoom);
                        if (!string.IsNullOrEmpty(exit.Name))
                            exit.Name += " " + door;
                        else
                            exit.Name = door.ToString();

                        switch (locks)
                        {
                            case 0:
                                break;
                            case 1:
                                exit.Flags |= (Exit.Closed | Exit.Door);
                                break;
                            case 2:
                                exit.Flags |= (Exit.Door | Exit.Closed | Exit.Locked);
                                break;
                            case 3:
                                exit.Flags |= (Exit.NoPass | Exit.Door | Exit.Closed |
                                               Exit.Locked);
                                break;
                            case 4:
                                exit.Flags |= (Exit.PickProof | Exit.Door | Exit.Closed |
                                               Exit.Locked);
                                break;
                            case 5:
                                exit.Flags |= (Exit.NoPass | Exit.PickProof | Exit.Door |
                                               Exit.Closed | Exit.Locked);
                                break;
                        }
                        room.Exits.Add(exit);
                        db.Exits.InsertOnSubmit(exit);
                    }
                    else if (letter == 'E')
                    {
                        //room.ExtraDescriptions.Add(reader.ReadString(),
                        reader.ReadString();
                        reader.ReadString();
                    }
                    else if (letter == 'O')
                    {
                        reader.ReadString();
                    }
                    else
                    {
                        log.Error("\troom flag " + letter + "  not found");
                        reader.UnRead();
                    }
                }

                //if (!currentArea.Rooms.ContainsKey(room.Id))
                db.Rooms.InsertOnSubmit(room);
                roomIndexes.Add(id, room);
                currentArea.Rooms.Add(room);
                count++;
            }
            log.Info("Loaded {0} rooms.", count);
        }

        private void LoadRomShops()
        {
            int count = 0;

            while (!reader.EndOfFile)
            {
                uint keeper = Convert.ToUInt32(reader.ReadNumber());

                if (keeper == 0)
                    break;

                for (int i = 0; i < 5; i++)
                    reader.ReadNumber();

                //Shop shop = new Shop();

                /*shop.ProfitBuy =*/
                Convert.ToInt16(reader.ReadNumber());
                /*shop.ProfitSell =*/
                Convert.ToInt16(reader.ReadNumber());
                /*shop.OpenHour =*/
                Convert.ToInt16(reader.ReadNumber());
                /*shop.CloseHour =*/
                Convert.ToInt16(reader.ReadNumber());

                count++;

                /*if (currentArea.Characters.ContainsKey(keeper))
                    {
                        currentArea.Characters[keeper].Shop = shop;
                    }*/
            }

            log.Info("Loading {0} shops not supported yet.", count);
        }

        private static string ConvertActTags(string format, int charIndex)
        {
            format = format.Replace("$n", "{" + charIndex + "}");

            format = format.Replace("$m", "{" + charIndex + ":M}");

            format = format.Replace("$s", "{" + charIndex + ":S}");

            format = format.Replace("$e", "{" + charIndex + ":E}");

            return format;
        }

        private static string ConvertActTags(string format, int charIndex, int victimIndex)
        {
            format = ConvertActTags(format, charIndex);


            format = format.Replace("$N", "{" + victimIndex + "}");

            format = format.Replace("$M", "{" + victimIndex + ":M}");

            format = format.Replace("$S", "{" + victimIndex + ":S}");

            format = format.Replace("$E", "{" + victimIndex + ":E}");

            return format;
        }


        private void LoadRomSocials()
        {
            int count = 0;
            Social soc = null;

            while (!reader.EndOfFile && ++count > 0)
            {
                if (soc != null)
                    db.Socials.InsertOnSubmit(soc);

                string temp = reader.ReadWord();

                if (temp == "#0")
                    break;

                soc = new Social();

                soc.Name = temp;

                reader.ReadLine();

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.CharNoArg = temp;

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.OthersNoArg = ConvertActTags(temp, 0);

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.CharFound = ConvertActTags(temp, 0, 1);

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.OthersFound = ConvertActTags(temp, 1, 0);

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.VictimFound = ConvertActTags(temp, 1, 0);

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.CharNotFound = temp;

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.CharSelf = temp;

                temp = reader.ReadLine();

                if (temp == "#")
                    continue;

                if (temp != "$")
                    soc.OthersSelf = ConvertActTags(temp, 0);
            }

            log.Info("Loaded {0} socials.", count);
        }

        private void LoadRomSpecials()
        {
            while (!reader.EndOfFile)
            {
                char letter;

                switch ((letter = reader.ReadLetter()))
                {
                    default:
                        log.Error("\timport specials: letter " + letter + " not found.");
                        break;

                    case 'S':
                        return;

                    case '*':
                        break;

                    case 'M':
                        reader.ReadNumber();
                        reader.ReadWord();
                        break;
                }

                reader.ReadLine();
            }
        }

        private long RomConvertActFlags()
        {
            long bits = reader.ReadFlag();
            var convertTable = new Dictionary<char, long>();
            long flags = 0;

            convertTable.Add('B', NonPlayer.Sentinel);
            convertTable.Add('F', NonPlayer.Aggressive);
            convertTable.Add('G', NonPlayer.StayArea);
            convertTable.Add('H', NonPlayer.Wimpy);

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    flags |= entry.Value;
                }
            }

            return flags;
        }

        private AffectType RomConvertAffectWhat()
        {
            int what = reader.ReadNumber();

            switch (what)
            {
                default:
                    return AffectType.None;

                case 17:
                    return AffectType.AC;
                case 13:
                    return AffectType.Hit;
                case 12:
                    return AffectType.Mana;
                case 14:
                    return AffectType.Move;
            }
        }

        private long RomConvertContainerFlags()
        {
            var convertTable = new Dictionary<char, long>();
            long flags = 0;
            long bits = reader.ReadFlag();

            /*
                convertTable.Add('A', ContainerFlags.Closeable);
                convertTable.Add('B', ContainerFlags.PickProof);
                convertTable.Add('C', ContainerFlags.Closed);
                convertTable.Add('D', ContainerFlags.Locked);
                */

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    flags |= entry.Value;
                }
            }
            return flags;
        }


        private long RomConvertExitFlags()
        {
            var convertTable = new Dictionary<char, long>();
            long flags = 0;
            long bits = reader.ReadFlag();

            convertTable.Add('A', Exit.Door);
            convertTable.Add('B', Exit.Closed);
            convertTable.Add('C', Exit.Locked);
            convertTable.Add('F', Exit.PickProof);
            convertTable.Add('G', Exit.PickProof);

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    flags |= entry.Value;
                }
            }
            return flags;
        }

        private long RomConvertExtraFlags()
        {
            var convertTable = new Dictionary<char, long>();
            long itemFlags = 0;
            long bits = reader.ReadFlag();

            // convertTable.Add('A', ItemFlags.Unknown);

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    itemFlags |= entry.Value;
                }
            }
            return itemFlags;
        }


        private void RomConvertFurnitureFlags(FurnitureObject furnItem)
        {
            long bits = reader.ReadFlag();
            long flags = 0;
            const long STAND_AT = (1L << 0);
            const long STAND_ON = (1L << 1);
            const long STAND_IN = (1L << 2);
            const long SIT_AT = (1L << 3);
            const long SIT_ON = (1L << 4);
            const long SIT_IN = (1L << 5);
            const long REST_AT = (1L << 6);
            const long REST_ON = (1L << 7);
            const long REST_IN = (1L << 8);
            const long SLEEP_AT = (1L << 9);
            const long SLEEP_ON = (1L << 10);
            const long SLEEP_IN = (1L << 11);

            if (bits.And(STAND_ON | STAND_AT | STAND_IN))
            {
                //flags |= FurnitureObject.Stand;
            }
            if (bits.And(REST_AT | REST_IN | REST_ON | SIT_AT | SIT_IN | SIT_ON))
            {
                //flags |= FurnitureObject.Sit;
            }
            if (bits.And(SLEEP_AT | SLEEP_IN | SLEEP_ON))
            {
                //flags |= FurnitureObject.Sleep;
            }
            furnItem.V3 = flags;

            if (bits.And(STAND_AT | REST_AT | SIT_AT | SLEEP_AT))
            {
                //furnItem.V2 = FurnitureType.At;
            }
            else if (bits.And(STAND_IN | REST_IN | SIT_IN | SLEEP_IN))
            {
                //furnItem.Type = FurnitureType.In;
            }
            else if (bits.And(STAND_ON | REST_ON | SIT_ON | SLEEP_ON))
            {
                //furnItem.Type = FurnitureType.On;
            }
        }


        private long RomConvertPortalFlags()
        {
            var convertTable = new Dictionary<char, long>();
            long flags = 0;
            long bits = reader.ReadFlag();

            //convertTable.Add('C', PortalFlags.GoWith);
            //convertTable.Add('E', PortalFlags.Random);

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    flags |= entry.Value;
                }
            }
            return flags;
        }

        private long RomConvertWeaponFlags()
        {
            return reader.ReadFlag();
        }

        private long RomConvertWearFlags()
        {
            var convertTable = new Dictionary<char, long>();
            long bits = reader.ReadFlag();
            long flags = 0;

            //convertTable.Add('P', Object.NoSacrifice);

            if (!bits.And(RomReader.FlagConvert('A')))
            {
                //flags |= Object.Static;
            }

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    flags |= entry.Value;
                }
            }
            return flags;
        }

        private Direction RomDirectionConvert()
        {
            int door = reader.ReadNumber();

            switch (door)
            {
                case 0:
                    return Direction.North;
                case 1:
                    return Direction.East;
                case 2:
                    return Direction.South;
                case 3:
                    return Direction.West;
                case 4:
                    return Direction.Up;
                case 5:
                    return Direction.Down;
                default:
                    throw new RomImportException("Invalid direction on room " + door);
            }
        }


        private ScrollPotionPillObject RomReadPotionScrollOrPill(ScrollPotionPillObject item)
        {
            item.V1 = reader.ReadNumber();
            var spells = new HashSet<Spell>();
            Spell spell = World.Abilities.FindSpellByName(reader.ReadWord()); // skill lookup
            if (spell != null) spells.Add(spell);
            spell = World.Abilities.FindSpellByName(reader.ReadWord()); // skill lookup
            if (spell != null) spells.Add(spell);
            spell = World.Abilities.FindSpellByName(reader.ReadWord());
            if (spell != null) spells.Add(spell);
            spell = World.Abilities.FindSpellByName(reader.ReadWord());
            if (spell != null) spells.Add(spell);
            item.V2 = spells;
            return item;
        }

        private WandStaffObject RomReadWandOrStaff(WandStaffObject item)
        {
            item.V1 = reader.ReadNumber();
            item.V2 = reader.ReadNumber();
            item.V3 = reader.ReadNumber();
            item.V4 = World.Abilities.FindSpellByName(reader.ReadWord());
            reader.ReadNumber();
            return item;
        }

        private long RomRoomFlagConvert()
        {
            var convertTable = new Dictionary<char, long>();

            long bits = reader.ReadFlag();

            long roomFlags = 0;

            // convertTable.Add('A', RoomFlags.Example);

            foreach (var entry in convertTable)
            {
                if (bits.And(RomReader.FlagConvert(entry.Key)))
                {
                    roomFlags |= entry.Value;
                }
            }

            return roomFlags;
        }

        private RoomType RomRoomSectorConvert()
        {
            int sector = reader.ReadNumber();

            switch (sector)
            {
                case 0:
                    return RoomType.Inside;
                case 1:
                    return RoomType.City;
                case 2:
                    return RoomType.Field;
                case 3:
                    return RoomType.Forest;
                case 4:
                    return RoomType.Hills;
                case 5:
                    return RoomType.Mountain;
                case 6:
                case 7:
                    return RoomType.Water;
                case 9:
                    return RoomType.Air;
                case 10:
                    return RoomType.Desert;
                default:
                    log.Error("\tunknown sector type " + sector);
                    return RoomType.Inside;
            }
        }

        #region Nested type: RomReader

        private class RomReader
        {
            private readonly string file;

            private int pos;

            public RomReader(string path)
            {
                file = File.ReadAllText(path);
                pos = 0;
            }

            public bool EndOfFile
            {
                get { return pos >= file.Length - 1; }
            }

            public static long FlagConvert(char letter)
            {
                long bitsum = 0;
                char i;

                if ('A' <= letter && letter <= 'Z')
                {
                    bitsum = 1;
                    for (i = letter; i > 'A'; i--)
                        bitsum *= 2;
                }
                else if ('a' <= letter && letter <= 'z')
                {
                    bitsum = 67108864; /* 2^26 */
                    for (i = letter; i > 'a'; i--)
                        bitsum *= 2;
                }

                return bitsum;
            }

            public char Read()
            {
                return file[pos++];
            }

            public long ReadFlag()
            {
                long number = 0;

                char c = ReadLetter();

                if (char.IsLetter(c)) /* ROM OLC */
                {
                    while (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
                    {
                        number += FlagConvert(c);
                        c = Read();
                    }
                }

                while (char.IsDigit(c))
                {
                    number = number*10 + c - '0';
                    c = Read();
                }

                if (c == '|')
                    number += ReadFlag();

                else if (c != ' ')
                    UnRead();

                return number;
            }

            public char ReadLetter()
            {
                char letter;

                do
                {
                    letter = Read();
                } while (char.IsWhiteSpace(letter));


                return letter;
            }

            public string ReadLine()
            {
                char letter = Read();

                var line = new StringBuilder();

                while (!EndOfFile && letter != '\n' && letter != '\r')
                {
                    line.Append(letter);
                    letter = Read();
                }

                while (!EndOfFile && (letter == '\n' || letter == '\r'))
                    letter = Read();

                UnRead();

                return line.ToString();
            }

            /*
                 * Read a number from a file.
                 */

            public int ReadNumber()
            {
                int number = 0;
                bool sign = false;
                char c = ReadLetter();

                if (c == '+')
                {
                    c = Read();
                }
                else if (c == '-')
                {
                    sign = true;
                    c = Read();
                }

                if (!char.IsDigit(c))
                {
                    log.Error("ReadNumber: bad format ({0}).", file.Substring(pos - 5, 10));
                }

                do
                {
                    number = number*10 + c - '0';
                    c = Read();
                } while (char.IsDigit(c));

                if (sign)
                    number = 0 - number;

                if (c == '|')
                    number += ReadNumber();
                else if (c != ' ')
                    UnRead();

                return number;
            }

            public string ReadString()
            {
                char c = ReadLetter();
                var buf = new StringBuilder();

                while (!EndOfFile && c != '~')
                {
                    switch (c)
                    {
                        default:
                            buf.Append(c);
                            break;
                        case '\n':
                            buf.Append(Environment.NewLine);
                            break;
                        case '\r':
                            break;
                    }
                    c = Read();
                }

                return buf.ToString();
            }

            public string ReadWord()
            {
                char cEnd = ReadLetter();
                var word = new StringBuilder();

                if (cEnd != '"' && cEnd != '\'')
                {
                    word.Append(cEnd);
                    cEnd = ' ';
                }

                while (!EndOfFile)
                {
                    char next = Read();

                    if (cEnd == ' ' ? char.IsWhiteSpace(next) : next == cEnd)
                    {
                        if (cEnd == ' ')
                            UnRead();

                        return word.ToString();
                    }

                    word.Append(next);
                }

                return word.ToString();
            }

            public void UnRead()
            {
                if (pos > 0) pos--;
            }
        }

        #endregion
    }

    public class RomImportException : Exception
    {
        public RomImportException(string msg) : base(msg)
        {
        }

        public RomImportException()
        {
        }
    }
}